feat: add .md endpoint for API reference pages#57
Conversation
Generate markdown from OpenAPI spec at /apis/{spec}/{operationId}.md
with authorization, parameters, request body, response schemas,
JSON examples, and cURL snippet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds an OpenAPI-to-Markdown documentation generation feature. A route guard prevents the default markdown handler from processing ChangesAPI Documentation Generation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
packages/chronicle/src/server/routes/apis/[...slug].md.ts (3)
95-96: 💤 Low valueContent-Type selection uses first key.
When multiple content types are defined in
requestBody.content, this implementation renders the first one fromObject.keys(). While modern JavaScript engines preserve insertion order, explicitly preferringapplication/json(if present) might be more predictable for API documentation.♻️ Proposed refinement
- const contentType = Object.keys(requestBody.content)[0] + const contentTypes = Object.keys(requestBody.content) + const contentType = contentTypes.includes('application/json') + ? 'application/json' + : contentTypes[0]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 95 - 96, The code currently picks the content type by taking the first key from requestBody.content (contentType = Object.keys(requestBody.content)[0]); change this to explicitly prefer "application/json" when present (e.g., if ("application/json" in requestBody.content) contentType = "application/json"; else contentType = Object.keys(requestBody.content)[0]) and then derive schema the same way (schema = contentType ? requestBody.content[contentType]?.schema as OpenAPIV3.SchemaObject : undefined), so that the variable contentType and schema resolution in this module prefer application/json but still fall back to the original behavior.
167-181: 💤 Low valueConsider consolidating field table rendering.
renderFieldTableandrenderResponseFieldTableare nearly identical except for column count. You could merge them with a boolean flag (e.g.,includeRequired) or column-spec parameter to reduce duplication.♻️ Example consolidation
function renderFieldTable( fields: SchemaField[], lines: string[], depth: number, includeRequired: boolean = true ) { const indent = ' '.repeat(depth) for (const f of fields) { const requiredCol = includeRequired ? ` | ${f.required ? 'Yes' : 'No'}` : '' lines.push(`| ${indent}\`${f.name}\` | ${f.type}${requiredCol} | ${f.description ?? ''} |`) if (f.children) renderFieldTable(f.children, lines, depth + 1, includeRequired) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 167 - 181, renderFieldTable and renderResponseFieldTable duplicate logic; consolidate them into a single function (e.g., renderFieldTable) that accepts an extra parameter (boolean includeRequired or a columns spec) to control whether the "Required" column is emitted, update all recursive calls (renderFieldTable → renderFieldTable(..., includeRequired)) and replace calls to renderResponseFieldTable to call the unified function with includeRequired=false; ensure the emitted Markdown row string builds the required column conditionally so table column counts remain correct.
19-21: ⚖️ Poor tradeoffConsider caching config and API specs.
Both
loadConfig()andloadApiSpecs()lack internal caching and perform operations on every invocation:loadConfig()re-parses the config, whileloadApiSpecs()performs disk I/O (fs.readFile) and re-parses YAML/JSON specs. For frequently accessed endpoints, caching these results at the handler or application level could improve performance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts around lines 19 - 21, The handler repeatedly calls loadConfig() and loadApiSpecs(), causing re-parsing and disk I/O on each request; modify the code to cache their results (e.g., module-level memoization or a simple in-memory cache with optional TTL/invalidation) so subsequent calls return the cached config and parsed specs instead of re-reading files; specifically, add caching logic around loadConfig() and loadApiSpecs() used before calling findApiOperation(specs, slug), ensure the cache can be invalidated when config or spec files change (or use a short TTL) and keep function signatures unchanged so callers like the route handler still call loadConfig() and loadApiSpecs() but get cached results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/chronicle/src/server/routes/apis/`[...slug].md.ts:
- Around line 95-96: The code currently picks the content type by taking the
first key from requestBody.content (contentType =
Object.keys(requestBody.content)[0]); change this to explicitly prefer
"application/json" when present (e.g., if ("application/json" in
requestBody.content) contentType = "application/json"; else contentType =
Object.keys(requestBody.content)[0]) and then derive schema the same way (schema
= contentType ? requestBody.content[contentType]?.schema as
OpenAPIV3.SchemaObject : undefined), so that the variable contentType and schema
resolution in this module prefer application/json but still fall back to the
original behavior.
- Around line 167-181: renderFieldTable and renderResponseFieldTable duplicate
logic; consolidate them into a single function (e.g., renderFieldTable) that
accepts an extra parameter (boolean includeRequired or a columns spec) to
control whether the "Required" column is emitted, update all recursive calls
(renderFieldTable → renderFieldTable(..., includeRequired)) and replace calls to
renderResponseFieldTable to call the unified function with
includeRequired=false; ensure the emitted Markdown row string builds the
required column conditionally so table column counts remain correct.
- Around line 19-21: The handler repeatedly calls loadConfig() and
loadApiSpecs(), causing re-parsing and disk I/O on each request; modify the code
to cache their results (e.g., module-level memoization or a simple in-memory
cache with optional TTL/invalidation) so subsequent calls return the cached
config and parsed specs instead of re-reading files; specifically, add caching
logic around loadConfig() and loadApiSpecs() used before calling
findApiOperation(specs, slug), ensure the cache can be invalidated when config
or spec files change (or use a short TTL) and keep function signatures unchanged
so callers like the route handler still call loadConfig() and loadApiSpecs() but
get cached results.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bd59ef61-cda4-4c75-84c2-7676fe52a75a
📒 Files selected for processing (2)
packages/chronicle/src/server/routes/[...slug].md.tspackages/chronicle/src/server/routes/apis/[...slug].md.ts
Summary
server/routes/apis/[...slug].md.tsgenerates markdown from OpenAPI spec[...slug].md.tsskips/apis/paths to avoid conflictsTest plan
bun run build:cli && bun run dev:examples:basic/apis/{spec}/{operationId}.md— returns markdown.mdURL.mdURLs still work unchanged🤖 Generated with Claude Code